Skip to content

fix(skills): reject an unknown flag instead of installing it as the skill source - #227

Merged
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/skill-install-stray-flag
Aug 3, 2026
Merged

fix(skills): reject an unknown flag instead of installing it as the skill source#227
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/skill-install-stray-flag

Conversation

@clawedassistant26

Copy link
Copy Markdown
Contributor

The bug

skill install takes the first unconsumed token as the source:

if (rest[i] === "--name") {  }
else if (!source) source = rest[i];

Nothing checks whether that token is a flag. So a flag skill does not know becomes the source, and the URL the user actually typed is dropped without a word:

$ moshcode skill install -s user https://github.com/example/real-skill.git
· installing skill s → -s across skills engines…
   claude    ✓ installed
   gemini    ✓ installed

A skill named s, from a source of -s. user and the URL are never looked at again.

The source is spliced verbatim into each engine's native argv, which is where it gets sharp:

GIT ARGV:    clone --depth 1 -s /home/u/.claude/skills/s
GEMINI ARGV: skills install -s --scope user

-s is git's own --shared, so git stops treating the last path as the destination and reads it as the repository:

$ git clone --depth 1 -s /home/u/.claude/skills/s
fatal: repository '/home/u/.claude/skills/s' does not exist

A typo lands in the same place — skill install --nmae my-skill <url> installs a skill called nmae from a source of --nmae and silently discards the URL.

The fix

Reject a source that still starts with - after parsing, and say which flag skill install does take. This is the guard mcp already applies to its own spec (src/integrations.mjs, "A token still starting with - at this point was never consumed as a flag…", added in #164) — same failure mode, same kind of argv splice, so the same rule rather than a new one.

$ moshcode skill install -s user https://github.com/example/real-skill.git
✗ unknown skill flag "-s" — skill install takes --name; a source that really starts with "-" must be written as ./-s
$ echo $?
1

The ./-s escape hatch is real, not decorative — git clone --depth 1 ./-s dest clones fine, verified.

Verification

Reproduced before touching code, with fake git/gemini bins on PATH logging their argv and a sandboxed HOME — the argv lines above are that log verbatim. Re-ran the same three cases after the fix: both bad cases now exit 1, and the control (skill install <url>) produces byte-identical argv to before.

Tests: new test/skill-stray-flag.test.mjs, 11 tests. 5 pin the bug and fail before this change; 6 are controls that pass in both directions (a normal URL still installs, --name still names, a ./path source is untouched, the pre-existing --name requires a value guard still wins, no-source still prints usage, an unknown verb still says unknown verb). They drive skillCommand through the run/installedSet seam it already exposes, so no new export and no signature change.

Suite: 905 → 916 tests, 727 passing, 0 failing.

Scope

Deliberately not fixed here, so the diff stays one thing:

  • Extra positionals are still dropped silently. skill install <url> extra junk ignores extra and junk. That is a real second gap, but it is a separate behaviour change with its own compatibility question, and it is not what makes the case above dangerous.
  • A stray flag after a valid source (skill install <url> -s user) is still ignored rather than rejected. mcp deliberately lets trailing positionals through as the command's own args; skill has no args concept, so the right answer there is arguable and worth its own change.

…kill source

`skill install` took the first unconsumed token as the source, so a flag it
does not know became the source itself and the URL the user typed was dropped:
`skill install -s user https://github.com/o/r.git` installs a skill called
`s` from a source of `-s`, and never looks at `user` or the URL again.

The source is spliced verbatim into each engine's native argv, so gemini gets
`skills install -s --scope user` and Claude gets
`git clone --depth 1 -s <skills-dir>/s`, where `-s` is git's own `--shared`
and makes git read the destination as the repository.

`mcp` already rejects a stray flag for this exact reason; this applies the
same guard to the same kind of splice.
@ralyodio
ralyodio merged commit cfbb836 into moshcoder:main Aug 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants